<?php

namespace hunma\utils\guzzle;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use hunma\exception\ApiException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use support\Log;

/**
 * HTTP 客户端工具类
 * 基于 GuzzleHttp/guzzle 封装，用于发起API请求
 */
class BaseHttpClient
{
    /**
     * Guzzle客户端实例
     * @var Client
     */
    protected $client;

    /**
     * 默认请求选项
     * @var array
     */
    protected $defaultOptions = [];

    /**
     * 是否记录请求日志
     * @var bool
     */
    protected $enableLogging = true;

    /**
     * 构造函数
     *
     * @param array $config Guzzle客户端配置
     * @param array $defaultOptions 默认请求选项
     */
    public function __construct(array $config = [], array $defaultOptions = [])
    {
        // 合并默认配置
        $config = array_merge([
            'verify'=>false,
            'timeout' => 30,
            'connect_timeout' => 10,
            'http_errors' => false, // 禁用Guzzle抛出异常，由我们自己处理
        ], $config);

        // 添加中间件用于日志记录
        if ($this->enableLogging) {
            $handlerStack = isset($config['handler']) ? $config['handler'] : HandlerStack::create();
            $handlerStack->push(Middleware::mapRequest(function (RequestInterface $request) {
                // 记录请求
                $this->logRequest($request);
                return $request;
            }));
            $handlerStack->push(Middleware::mapResponse(function (ResponseInterface $response) {
                // 记录响应
                $this->logResponse($response);
                return $response;
            }));
            $config['handler'] = $handlerStack;
        }
        $this->client = new Client($config);
        $this->defaultOptions = $defaultOptions;
    }

    /**
     * 发送GET请求
     *
     * @param string $url 请求URL
     * @param array $query 查询参数
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容(已解析的JSON或原始响应)
     * @throws ApiException 请求失败时抛出异常
     */
    public function get($url, array $query = [], array $headers = [], array $options = [])
    {
        $options = array_merge($options, [
            'headers' => $headers,
            'query' => $query
        ]);

        return $this->request('GET', $url, $options);
    }

    /**
     * 发送POST请求（JSON）
     *
     * @param string $url 请求URL
     * @param array $data 请求数据
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function postJson($url, array $data = [], array $headers = [], array $options = [])
    {
        $headers = array_merge([
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ], $headers);

        $options = array_merge($options, [
            'headers' => $headers,
            'json' => $data
        ]);

        return $this->request('POST', $url, $options);
    }

    /**
     * 发送POST表单请求
     *
     * @param string $url 请求URL
     * @param array $formParams 表单参数
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function postForm($url, array $formParams = [], array $headers = [], array $options = [])
    {
        $headers = array_merge([
            'Content-Type' => 'application/x-www-form-urlencoded',
        ], $headers);

        $options = array_merge($options, [
            'headers' => $headers,
            'form_params' => $formParams
        ]);

        return $this->request('POST', $url, $options);
    }

    /**
     * 发送带有文件的POST请求
     *
     * @param string $url 请求URL
     * @param array $multipart Multipart表单数据
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function postMultipart($url, array $multipart = [], array $headers = [], array $options = [])
    {
        $options = array_merge($options, [
            'headers' => $headers,
            'multipart' => $multipart
        ]);

        return $this->request('POST', $url, $options);
    }

    /**
     * 发送PUT请求
     *
     * @param string $url 请求URL
     * @param array $data 请求数据
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function put($url, array $data = [], array $headers = [], array $options = [])
    {
        $headers = array_merge([
            'Content-Type' => 'application/json',
            'Accept' => 'application/json',
        ], $headers);

        $options = array_merge($options, [
            'headers' => $headers,
            'json' => $data
        ]);

        return $this->request('PUT', $url, $options);
    }

    /**
     * 发送DELETE请求
     *
     * @param string $url 请求URL
     * @param array $query 查询参数
     * @param array $headers 请求头
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function delete($url, array $query = [], array $headers = [], array $options = [])
    {
        $options = array_merge($options, [
            'headers' => $headers,
            'query' => $query
        ]);

        return $this->request('DELETE', $url, $options);
    }

    /**
     * 发送HTTP请求
     *
     * @param string $method 请求方法
     * @param string $url 请求URL
     * @param array $options 请求选项
     * @return mixed 响应内容
     * @throws ApiException 请求失败时抛出异常
     */
    public function request($method, $url, array $options = [])
    {
        try {
            // 合并默认选项
            $options = array_merge($this->defaultOptions, $options);
            // 发送请求
            $response = $this->client->request($method, $url, $options);

            // 获取状态码
            $statusCode = $response->getStatusCode();

            // 获取响应内容
            $body = $response->getBody()->getContents();

            // 尝试解析JSON响应
            $decodedBody = $this->parseJsonResponse($body);

            // 检查响应状态码
            if ($statusCode < 200 || $statusCode >= 300) {
                $errorMessage = is_array($decodedBody) && isset($decodedBody['error']['message'])
                    ? $decodedBody['error']['message']
                    : "请求失败: HTTP状态码 $statusCode";

                throw new ApiException($errorMessage, $statusCode);
            }

            // 返回解析后的数据或原始响应
            return $decodedBody ?? $body;

        } catch (RequestException $e) {
            // 处理请求异常
            $this->handleRequestException($e);
        } catch (GuzzleException $e) {
            // 处理Guzzle异常
            $this->handleGuzzleException($e);
        }
    }

    /**
     * 使用Bearer令牌认证
     *
     * @param string $accessToken 访问令牌
     * @return $this
     */
    public function withBearerToken($accessToken)
    {
        $this->defaultOptions['headers']['Authorization'] = 'Bearer ' . $accessToken;
        return $this;
    }

    /**
     * 使用Basic认证
     *
     * @param string $username 用户名
     * @param string $password 密码
     * @return $this
     */
    public function withBasicAuth($username, $password)
    {
        $this->defaultOptions['auth'] = [$username, $password];
        return $this;
    }

    /**
     * 解析JSON响应
     *
     * @param string $body 响应体
     * @return array|null 解析后的JSON或null
     */
    protected function parseJsonResponse($body)
    {
        if (empty($body)) {
            return null;
        }

        $data = json_decode($body, true);
        if (json_last_error() === JSON_ERROR_NONE) {
            return $data;
        }

        return null;
    }

    /**
     * 处理请求异常
     *
     * @param RequestException $e 请求异常
     * @throws ApiException
     */
    protected function handleRequestException(RequestException $e)
    {
        $response = $e->getResponse();
        $statusCode = $response ? $response->getStatusCode() : 0;
        $body = $response ? $response->getBody()->getContents() : '';

        $decoded = $this->parseJsonResponse($body);
        $errorMessage = $decoded && isset($decoded['error']['message'])
            ? $decoded['error']['message']
            : $e->getMessage();

        throw new ApiException("API请求失败: " . $errorMessage, $statusCode ?: 500);
    }

    /**
     * 处理Guzzle异常
     *
     * @param GuzzleException $e Guzzle异常
     * @throws ApiException
     */
    protected function handleGuzzleException(GuzzleException $e)
    {
        throw new ApiException("请求异常: " . $e->getMessage(), 500);
    }

    /**
     * 记录请求日志
     *
     * @param RequestInterface $request
     */
    protected function logRequest(RequestInterface $request)
    {
        $method = $request->getMethod();
        $uri = $request->getUri();

        $headers = [];
        foreach ($request->getHeaders() as $name => $values) {
            if (strtolower($name) === 'authorization') {
                // 敏感信息不完全记录
                $headers[$name] = 'Bearer ***';
            } else {
                $headers[$name] = implode(', ', $values);
            }
        }

        $body = (string) $request->getBody();
        // 如果正文过大，只记录部分
        if (strlen($body) > 1000) {
            $body = substr($body, 0, 1000) . '... (截断)';
        }

        Log::channel('guzzlehttp')->info("发送HTTP请求 - " . date('Y-m-d H:i:s'), [
            'method' => $method,
            'uri' => (string) $uri,
            'headers' => $headers,
            'body' => $body
        ]);
    }

    /**
     * 记录响应日志
     *
     * @param ResponseInterface $response
     */
    protected function logResponse(ResponseInterface $response)
    {
        $statusCode = $response->getStatusCode();
        $headers = [];

        foreach ($response->getHeaders() as $name => $values) {
            $headers[$name] = implode(', ', $values);
        }

        $body = (string) $response->getBody();
        // 重置流指针，避免后续读取时流已经被消耗
        if ($response->getBody()->isSeekable()) {
            $response->getBody()->rewind();
        }

        // 如果正文过大，只记录部分
        if (strlen($body) > 1000) {
            $body = substr($body, 0, 1000) . '... (截断)';
        }

        $logLevel = $statusCode >= 400 ? 'error' : 'info';
        Log::channel('guzzlehttp')->$logLevel("收到HTTP响应 - " . date('Y-m-d H:i:s'), [
            'status_code' => $statusCode,
            'headers' => $headers,
            'body' => $body
        ]);
    }
}